<?php
class AuditableBehavior extends CActiveRecordBehavior {
	private $_attributes = null;

	private $_initialAttributes = array();
	private $_changedAttributes = array();

	public function setAttributes($attributes) {
		if (is_string($attributes))
			$attributes = preg_split('/[\s,]+/', $attributes, -1, PREG_SPLIT_NO_EMPTY);
		$this->_attributes = $attributes;
	}

	private function setInitialAttributes() {
		$object = $this->getOwner();
		foreach ($this->_attributes as $attribute) {
			$this->_initialAttributes[$attribute] = $object->getAttribute($attribute);
		}
	}

	public function afterFind($event) {
		$this->setInitialAttributes();
	}

	public function beforeValidate($event) {
		$validators = $this->getOwner()->getValidatorList();
		foreach ($validators as $validator) {
			if ($validator instanceof AuditableValidator) {
				return;
			}
		}
		$validator = CValidator::createValidator('AuditableValidator', $this->getOwner(),
			$this->_attributes, array(
				'auditor' => $this,
			));
		$validators->add($validator);
	}

	public function beforeSave($event) {
		if (!isset(Yii::app()->user)) {
			return;
		}
		if (empty($this->_changedAttributes)) {
			return;
		}
		if (Yii::app()->user->isGuest) {
			throw new CHttpException(401, 'Guest cannot update auditable item.');
		}
		$auditableLog = new AuditableLog();
		if ($this->getOwner()->isNewRecord) {
			$auditableLog->change = serialize($this->_changedAttributes);
			$auditableLog->action = AuditableLog::ACTION_CREATE;
		} else {
			$changes = array();
			foreach ($this->_changedAttributes as $k => $v) {
				$changes[$k] = array(
					$this->_initialAttributes[$k],
					$v
				);
			}
			$auditableLog->change = serialize($changes);
			$auditableLog->action = AuditableLog::ACTION_UPDATE;
			$auditableLog->modelId = $this->getOwner()->getPrimaryKey();
		}
		$auditableLog->modelClass = get_class($this->getOwner());
		$auditableLog->userId = Yii::app()->user->id;
		$auditableLog->time = new CDbExpression('NOW()');
		$auditableLog->save();
	}

	public function reset() {
		$this->_initialAttributes = array();
		$this->_changedAttributes = array();
		$this->setInitialAttributes();
	}

	public function afterSave($event) {
		$this->reset();
	}

	public function beforeDelete($event) {
		if (!isset(Yii::app()->user)) {
			return;
		}
		if (Yii::app()->user->isGuest) {
			throw new CHttpException(401, 'Guest cannot update auditable item.');
		}
		$auditableLog = new AuditableLog();
		$auditableLog->change = serialize($this->_initialAttributes);
		$auditableLog->action = AuditableLog::ACTION_DELETE;
		$auditableLog->modelId = $this->getOwner()->getPrimaryKey();
		$auditableLog->modelClass = get_class($this->getOwner());
		$auditableLog->userId = Yii::app()->user->id;
		$auditableLog->time = new CDbExpression('NOW()');
		$auditableLog->save();
	}

	public function afterDelete($event) {
		$this->reset();
	}

	public function auditChange($object, $attribute) {
		$newValue = $object->getAttribute($attribute);
		if (isset($this->_initialAttributes[$attribute])
			&& $newValue == $this->_initialAttributes[$attribute] && $newValue !== null) {
			return;
		}
		$this->_changedAttributes[$attribute] = $newValue;
	}
}

class AuditableValidator extends CValidator {
	private $_auditor;

	public function setAuditor($auditor) {
		$this->_auditor = $auditor;
	}

	protected function validateAttribute($object, $attribute) {
		$this->_auditor->auditChange($object, $attribute);
	}
}
